use std::fs::File;
use std::io::{Write, BufReader, BufRead};

/// ### The databuf store kv data
/// ```rust
/// use iii::BufData;
/// let mut cc = BufData::new();
/// cc.chgvalue("c3", "ccc3");
/// cc.chgvalue("c5", "5c");
/// if let Some(v) = cc.getvalue("c3"){
///     println!("K:{},V:{}","c3",v);
/// }
/// cc.save(None);
/// ```
#[derive(Default)]
pub struct BufData{
    filepath:String,
    data:Vec<DataType>,
}
enum DataType {
    Notes(String),
    Data((String,String)),
}

impl BufData {
    /// Create a empty data squence in memory
    pub fn new()->Self{
        Self { filepath:"default.iii".to_string(),..Default::default() }
    }
    /// ### load config file
    /// 
    /// ```rust
    /// use iii::BufData;
    /// let mut cc = BufData::loadfromiii("default.iii");
    /// ```
    pub fn loadfromiii(filepath:&str)->Result<Self,&'static str>{
        let mut data = Vec::new();
        if let Ok(file)=File::open(filepath){
            for idx in BufReader::new(file).lines(){
                if let Ok(iidx) = idx{
                    if iidx.trim().starts_with("#"){
                        data.push(DataType::Notes(iidx));
                    }else{
                        let cc:Vec<&str> = iidx.trim().split("=").collect();
                        if cc.len() == 2{
                            data.push(DataType::Data((cc[0].trim().to_string(),cc[1].trim().to_string())));
                        }
                    }
                }
            }
            Ok(Self{filepath:filepath.to_string(),data:data})
        }else{
            Err("load file fail")
        }
        
    }
    
    fn onlywrite(&self,path:&str)->Result<(),&'static str>{
        if let Ok(mut f) = File::create(path){
            let mut bufstr = String::new();
            for idx in &self.data{
                match idx {
                    DataType::Data((s1,s2))=>{
                        bufstr.push_str(&format!("{} = {}\r\n",s1,s2));
                    },
                    DataType::Notes(commet)=>{
                        bufstr.push_str(&commet);
                        bufstr.push_str("\r\n");
                    },
                }
            }
            if let Ok(_) = f.write(bufstr.as_bytes()){
                return Ok(());
            }
        }
        Err("write err")

    }
    
    /// ### save config to file 
    /// if newfile is None,use loaded file's path to save 
    /// 
    /// ```rust
    /// use iii::BufData;
    /// if let Ok(mut cc) = BufData::loadfromiii("default.iii"){
    ///     cc.chgvalue("c3", "ccc3");
    ///     cc.chgvalue("c5", "5c");
    ///     if let Ok(_) = cc.save(None){
    ///         println!("file write success")
    ///     };
    ///     if let Ok(_) = cc.save(Some("file1.iii")){
    ///         println!("file1.iii write success")
    ///     }
    /// }
    /// ```
    pub fn save(&self,newfilepath:Option<&str>)->Result<(),&'static str>{
        if let Some(pth) = newfilepath{
            self.onlywrite(pth)
        }else{
            self.onlywrite(&self.filepath)
        }
    }
    /// ### read value from file by key
    /// ```rust
    /// use iii::BufData;
    /// if let Ok(cc) = BufData::loadfromiii("default.iii"){
    ///     cc.chgvalue("c2", "33");
    ///     cc.chgvalue("c4", &true.to_string());
    ///     let Ok(value) = cc.getvalue1::<u32>("c2").unwrap();
    ///     assert!(33==value);
    ///     let Ok(value) = cc.getvalue1::<bool>("c4").unwrap();
    ///     assert!(true==value);
    /// };
    /// ```
    pub fn getvalue<T: std::str::FromStr>(&self,key:&str)->Result<T,()>{
        for idx in &self.data{
            if let DataType::Data((s1,s2))=idx{
                if &key.to_string() == s1{
                    if let Ok(ss) = s2.parse::<T>(){
                        return Ok(ss);
                    }
                }
            }
        }
        return Err(());
    }
    /// ### write value or change value
    /// ```rust
    /// use iii::BufData;
    /// if let Ok(mut cc) = BufData::loadfromiii("default.iii"){
    ///     cc.chgvalue("c3", "ccc3");
    ///     cc.chgvalue("c5", 233);
    ///     if let Ok(_) = cc.save(None){
    ///         println!("file write success")
    ///     };
    /// };
    /// ```
    pub fn chgvalue<T:std::fmt::Display>(&mut self,key:&str,value:T){
        for idx in &mut self.data{
            if let DataType::Data((s1,s2))=idx{
                if &key.to_string() == s1{
                    s2.clear();
                    s2.push_str(&value.to_string());
                    return;
                }
            }
        }
        self.data.push(DataType::Data((key.to_string(),value.to_string())));
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn crate1(){
        let mut cc = BufData::new();
        cc.chgvalue("c1", "value");
        cc.chgvalue("c2", "value2");
        cc.chgvalue("c3", "value3");
        if let Ok(_) = cc.save(None){
            println!("file write success")
        };
    }
    #[test]
    fn readwrite(){
        if let Ok(mut cc) = BufData::loadfromiii("default.iii"){
            cc.chgvalue("c3", "ccc3");
            cc.chgvalue("c5", "5c");
            if let Ok(_) = cc.save(None){
                println!("file write success")
            };
        };
    }

    #[test]
    fn readvalue(){
        if let Ok(cc) = BufData::loadfromiii("default.iii"){
            if let Ok(v) = cc.getvalue::<String>("c2"){
                println!("K:{},V:{}","c2",v);
            }
            if let Ok(v) = cc.getvalue::<String>("c5"){
                println!("K:{},V:{}","c5",v);
            }
        };
    }
    #[test]
    fn getnumbervalue()->Result<(),()>{
        let mut cc = BufData::new();
        cc.chgvalue("c2", "33");
        cc.chgvalue("c4", &true.to_string());
        let Ok(value) = cc.getvalue::<u32>("c2") else {return Err(())};
        assert!(33==value);
        let Ok(value) = cc.getvalue::<bool>("c4") else {return Err(());};
        assert!(true==value);
        Ok(())
    }
}
